package com.hra.camera2demo;

import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Log;
import android.view.Surface;

public class CoordinatesTransform {
    private static final String TAG = "CoordinatesTransformDebug";

    public static Rect uiToSensor(Point p,
                                  Rect previewArea,
                                  int activityOrientation,
                                  float regionRatio,
                                  Rect cropRegion,
                                  CameraCharacteristics characteristics) {
        Log.d(TAG, "uiToSensor1, point = (" + p.x + ", " + p.y + "); "
                + "previewArea = (" + previewArea.left + ", " + previewArea.top
                + ", " + previewArea.right + ", " + previewArea.bottom + "); "
                + "cropRegion = (" + cropRegion.width() + ", " + cropRegion.height() + ")");
        // Normalize coordinates to [0,1]
        float[] points = new float[2];
        points[0] = (float) (p.x - previewArea.left) / previewArea.width();
        points[1] = (float) (p.y - previewArea.top) / previewArea.height();
        // Rotate coordinates to portrait orientation.
        Matrix rotationMatrix = new Matrix();
        rotationMatrix.setRotate(activityOrientation, 0.5f, 0.5f);
        rotationMatrix.mapPoints(points);

        // Invert X coordinate on front camera since the display is mirrored.
        if (characteristics.get(CameraCharacteristics.LENS_FACING)
                == CameraCharacteristics.LENS_FACING_FRONT) {
            points[0] = 1 - points[0];
        }
        int sensorOrientation = characteristics.get(
                CameraCharacteristics.SENSOR_ORIENTATION);
        // Compute the output MeteringRectangle in sensor space.
        // Crop region itself is specified in sensor coordinates.
        // Normalized coordinates, now rotated into sensor space.
        PointF nsc = normalizedSensorCoordsForNormalizedDisplayCoords(
                points[0], points[1], sensorOrientation);

        Rect meteringRegion = normalizedPreviewTransformedToSensor(
                nsc, regionRatio, previewArea, cropRegion);

        Log.d(TAG, "uiToSensor1, resultRegion = (" + meteringRegion.left + ", "
                + meteringRegion.top + ", " + meteringRegion.right + ", "
                + meteringRegion.bottom + ")");
        return meteringRegion;
    }

    private static PointF normalizedSensorCoordsForNormalizedDisplayCoords(
            float nx, float ny, int sensorOrientation) {
        switch (sensorOrientation) {
            case 0:
                return new PointF(nx, ny);
            case 90:
                return new PointF(ny, 1.0f - nx);
            case 180:
                return new PointF(1.0f - nx, 1.0f - ny);
            case 270:
                return new PointF(1.0f - ny, nx);
            default:
                return null;
        }
    }

    private static Rect normalizedPreviewTransformedToSensor(PointF nsc, float regionRatio,
                                                             Rect previewArea, Rect cropRegion) {
        // Compute half side length in pixels.
        int minCropEdge = Math.min(cropRegion.width(), cropRegion.height());
        int halfSideLength = (int) (0.5f * regionRatio * minCropEdge);
        double previewRatio = previewArea.width() > previewArea.height() ?
                ((double) previewArea.width() / previewArea.height()) :
                ((double) previewArea.height() / previewArea.width());
        double cropRatio = (double) cropRegion.width() / cropRegion.height();
        int cropResizeWidth = cropRegion.width();
        int cropResizeHeight = cropRegion.height();
        if (previewRatio > cropRatio) {
            cropResizeHeight = (int) (cropResizeWidth / previewRatio);
        } else {
            cropResizeWidth = (int) (cropResizeHeight * previewRatio);
        }
        int deltaCropX = (cropRegion.width() - cropResizeWidth) / 2;
        int deltaCropY = (cropRegion.height() - cropResizeHeight) / 2;
        int xCenterSensor = (int) (cropRegion.left + nsc.x * cropResizeWidth + deltaCropX);
        int yCenterSensor = (int) (cropRegion.top + nsc.y * cropResizeHeight + deltaCropY);
        Rect restrictionRegion = new Rect(cropRegion.left + deltaCropX,
                cropRegion.top + deltaCropY,
                cropRegion.right - deltaCropX,
                cropRegion.bottom - deltaCropY);

        int touchSensorLeft = clamp(xCenterSensor - halfSideLength,
                restrictionRegion.left, restrictionRegion.right);
        int touchSensorTop = clamp(yCenterSensor - halfSideLength,
                restrictionRegion.top, restrictionRegion.bottom);
        int touchSensorRight = clamp(xCenterSensor + halfSideLength,
                restrictionRegion.left, restrictionRegion.right);
        int touchSensorBottom = clamp(yCenterSensor + halfSideLength,
                restrictionRegion.top, restrictionRegion.bottom);

        return new Rect(touchSensorLeft, touchSensorTop, touchSensorRight, touchSensorBottom);
    }

    private static int clamp(int x, int min, int max) {
        if (x > max) {
            return max;
        }
        return Math.max(x, min);
    }

    public static int getDisplayRotation(Activity activity) {
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        switch (rotation) {
            case Surface.ROTATION_90:
                return 90;
            case Surface.ROTATION_180:
                return 180;
            case Surface.ROTATION_270:
                return 270;
            case Surface.ROTATION_0:
            default:
                return 0;
        }
    }

    public static float lerp(float a, float b, float t) {
        return a + t * (b - a);
    }
}
